﻿using Dyao.Book.Contract;
using Dyao.Book.Core;
using Foundation.Core;

namespace Dyao.Book.Application
{
    /// <summary>
    /// 任务中心：单线程任务上下文
    /// </summary>
    public class TaskCenterSingleContext : TaskCenterExtend, ITaskCenterContext
    {
        /// <summary>
        /// 更新数据库进度时取余计算的除数
        /// </summary>
        private int _progressDivider = 20;

        private int[] _updateHilo;

        /// <summary>
        /// 分页任务占总任务量的比例
        /// </summary>
        private int _pagingProgressProportion = 100;

        private Func<Task> _onUpdate;

        private Func<Task<string>> _getParams;

        public TaskCenterSingleContext(TaskCenterDto taskModel, IServiceProvider serviceProvider)
        {
            TaskModel = taskModel;
            ServiceProvider = serviceProvider;
        }

        public IServiceProvider ServiceProvider { get; }

        public TaskCenterDto TaskModel { get; protected set; }

        /// <summary>
        /// 总页数
        /// </summary>
        public virtual int TotalPageCount { get; protected set; }

        /// <summary>
        /// 分页任务占总任务量的比例，默认是100 (既总进度全部由分页执行构成)。
        /// 分页任务占比用法，比如：有一个任务，由3个阶段组成：
        /// 1、解析Excel，获得Excel数据；
        /// 2、根据数据进行分页批量更新；
        /// 3、发送通知、event等事项
        /// 假设将任务3个阶段设置占比：第1阶段：15, 第2阶段：80， 第3阶段：5。
        /// 那么在执行任务过程中，
        /// 第1阶段结束后，调用 AddProgress(15),更新进度；
        /// 第2阶段开始时调用，SetPageCount 设置分页任务的总页数；
        /// 第2阶段循环时可调用AddPagingProgress 方法，更新任务的进度；
        /// 第3阶段结束，调用AddProgress(5),更新进度；
        /// 这样可以灵活更新任务进度。
        /// </summary>
        public virtual int PagingProgressProportion => _pagingProgressProportion;

        /// <summary>
        /// 执行进度
        /// </summary>
        public virtual decimal ExcutingProgress { get; protected set; }

        /// <summary>
        /// 执行结果消息提示
        /// </summary>
        public virtual string ResultMessage { get; set; }

        public Func<Task> OnUpdate
        {
            set { _onUpdate = value; }
        }

        /// <summary>
        /// 设置总页数
        /// </summary>
        /// <param name="pageCount"></param>
        public virtual void SetPageCount(int pageCount)
        {
            this.TotalPageCount = pageCount;
        }

        public virtual void SetPageCount(int totalCount, int pageSize)
        {
            if (pageSize <= 0) throw new ArgumentException($"{nameof(pageSize)}必须大于0。", nameof(pageSize));

            var pageCount = (int)Math.Ceiling(totalCount * 1.0d / pageSize);
            SetPageCount(pageCount);
        }

        /// <summary>
        /// 设置分页任务在总任务中的占比。范围：0~100。
        /// 分页任务占比用法，比如：有一个任务，由3个阶段组成：
        /// 1、解析Excel，获得Excel数据；
        /// 2、根据数据进行分页批量更新；
        /// 3、发送通知、event等事项
        /// 假设将任务3个阶段设置占比：第1阶段：15, 第2阶段：80， 第3阶段：5。
        /// 那么在执行任务过程中，
        /// 第1阶段结束后，调用<see cref="AddProgressAsync"/>传入参数值15,更新进度；
        /// 第2阶段开始时调用，<see cref="SetPageCount(int)"/>或者<see cref="SetPageCount(int, int)"/> 设置分页任务的总页数；
        /// 第2阶段循环时可调用<see cref="AddPagingProgressAsync"/>，更新任务分页进度；
        /// 第3阶段结束，调用<see cref="AddProgressAsync"/>传入参数值5,更新进度（如果最后一个阶段之后没有任何业务逻辑，也可以不更新，任务成功后会自动更新进度）；
        /// 这样可以灵活更新任务进度。
        /// </summary>
        /// <param name="proportion"></param>
        public virtual void SetPagingProgressProportion(int proportion)
        {
            if (proportion < 0 || proportion > 100)
            {
                throw new BusinessException(CommonExceptionCode.TaskCenterSingleContextPagingProgressProportionRangeError, "分页任务占比不能小于零，大于100。");
            }

            this._pagingProgressProportion = proportion;
        }

        /// <summary>
        /// 更新分页进度
        /// </summary>
        /// <returns></returns>
        public virtual Task AddPagingProgressAsync()
        {
            var progress = CalcOnePageProgress();

            return AddProgressAsync(progress);
        }

        /// <summary>
        /// 增加进度
        /// </summary>
        /// <param name="progress"></param>
        /// <returns></returns>
        public virtual Task AddProgressAsync(decimal progress)
        {
            if (progress > 0)
            {
                this.ExcutingProgress += progress;
            }

            return UpdateIfSuitableAsync();
        }

        public Task<string> GetRequestParametersStringAsync()
        {
            return _getParams();
        }

        public void SetLoadRequestParams(Func<Task<string>> func)
        {
            _getParams = func;
        }

        /// <summary>
        /// 计算1页进度占比
        /// </summary>
        /// <returns></returns>
        protected virtual decimal CalcOnePageProgress()
        {
            if (this.TotalPageCount == 0)
            {
                throw new BusinessException(CommonExceptionCode.TaskCenterSingleContextPageCountNotSet, "请先设置总页数！");
            }

            var progress = Convert.ToDecimal(Math.Round(this.PagingProgressProportion * 1.0 / TotalPageCount, 2));
            return progress;
        }

        /// <summary>
        /// 如果满足条件，触发更新
        /// </summary>
        /// <returns></returns>
        protected virtual Task UpdateIfSuitableAsync()
        {
            if (_onUpdate != null && _progressDivider > 0)
            {
                if (_updateHilo == null) _updateHilo = new[] { 0, _progressDivider };

                //满足条件时，触发更新。 例如：_progressDivider=20， 进度 20、40、60、80关键区段，落在2个区段其中，会更新一次。
                var progress = this.ExcutingProgress;
                if (progress > 0 && progress < 100 &&
                    (progress > _updateHilo[1] || (_updateHilo[0] <= progress && progress < _updateHilo[1])))
                {
                    while (progress >= _updateHilo[0])
                    {
                        _updateHilo[0] = _updateHilo[1];
                        _updateHilo[1] = _updateHilo[1] + _progressDivider;
                    }

                    return _onUpdate();
                }
            }

            return Task.CompletedTask;
        }

    }
}
